import { extend } from "../shared";
let activeEffect: any;
let shouldTrack: any;
export class ReactiveEffect {
  public _fn: any;
  public deps: Array<any> = [];
  public active = true;
  public onStop?: () => void;
  constructor(fn, public scheduler?) {
    this._fn = fn;
  }

  run() {
    if (!this.active) {
      return this._fn();
    }
    shouldTrack = true;
    activeEffect = this;

    const result = this._fn();
    shouldTrack = false;
    return result;
  }

  stop() {
    if (this.active) {
      cleanupEffect(this);
      if (this.onStop) {
        this.onStop();
      }
      this.active = false;
    }
  }
}

// stop的含义是收集的依赖全部清除
function cleanupEffect(effect) {
  effect.deps.forEach((dep) => {
    dep.delete(effect);
  });
  effect.deps.length = 0;
}

export function effect(fn, options: any = {}) {
  const _effect = new ReactiveEffect(fn, options.scheduler);
  // TODO
  extend(_effect, options);
  _effect.run();
  const runner: any = _effect.run.bind(_effect);
  runner.effect = _effect;
  return runner;
}

const targetMap = new WeakMap();
export function track(target, key) {
  if (!isTracking()) return;
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  let depSet = depsMap.get(key);
  if (!depSet) {
    depSet = new Set();
    // activeEffect && activeEffect.deps.push(depSet);
    depsMap.set(key, depSet);
  }
  trackEffect(depSet);
}
export function trackEffect(dep) {
  const hasEffect = dep.has(activeEffect);
  // if (!hasEffect) {
  //   dep.add(activeEffect);
  // }
  if (hasEffect) return;

  dep.add(activeEffect);
  activeEffect.deps.push(dep);
}

export function isTracking() {
  return shouldTrack && activeEffect !== undefined;
}

export function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (depsMap) {
    const depSet = depsMap.get(key);
    if (depSet) {
      triggerEffect(depSet);
    }
  }
}

export function triggerEffect(dep) {
  for (const effect of dep) {
    if (effect.scheduler) {
      effect.scheduler();
    } else {
      effect.run();
    }
  }
}

export function stop(runner) {
  runner.effect.stop();
}
